
#include "common.h"
#include "cache_control.h"
#include "server.h"

static void cache_control_parse(cache_control_t *cc, char *value, int size);
static time_t cache_age(cache_file_t *cache);


#define LM_FACTOR 0.2
#define DEFAULT_CACHE_MIN_LIFETIME 60
#define DEFAULT_CACHE_MAX_LIFETIME (7*24*60*60)
#define MAX_STALE "max-stale"
#define MIN_STALE "min-stale"
#define MIN_FRESH "min-fresh"
#define MAX_AGE "max-age"
#define NO_CACHE "no-cache"
#define NO_STORE "no-store"
#define ONLY_IF_CACHED "only-if-cached"
#define PUBLIC "public"
#define PRIVATE "private"
#define NO_TRANSFORM "no-transform"
#define MUST_REVALIDATE "must-revalidate"
#define PROXY_REVALIDATE "proxy-revalidate"
#define S_MAXAGE "s-maxage"


/* computer cache age */
static time_t cache_age(cache_file_t *cache)
{
	if(!cache) 
		return -1;

	int apparent_age;
	int corrected_apparent_age;
	int response_delay_estimate;
	int age_when_document_arrived_at_our_cache;
	int how_long_copy_has_been_in_our_cache ;
	int age;
	struct timeval now;

	if(cache->time.date < 0) {
		debug_log(3, "response Date %d < 0, invalid\n", cache->time.date);
		return 0;
	}

	current_time_get(&now);
	apparent_age = (cache->time.response_got - cache->time.date) > 0 ? \
				   (cache->time.response_got - cache->time.date) : 0;
	corrected_apparent_age = cache->time.header_age > apparent_age ? \
							 cache->time.header_age : apparent_age;
	response_delay_estimate  = cache->time.response_got - cache->time.request_issued;

	age_when_document_arrived_at_our_cache = corrected_apparent_age + response_delay_estimate;
	how_long_copy_has_been_in_our_cache = now.tv_sec - cache->time.response_got;

	age = age_when_document_arrived_at_our_cache + how_long_copy_has_been_in_our_cache;

	debug_log(3, "cache_age %ld\n", age);
	return age;
}

/* after cache time and cache-control parsed */
time_t cache_freshness_lifetime(cache_file_t *cache)
{
	if(!cache) 
		return -1;

	int heuristic = 0;
	int server_freshness_limit;
	int time_since_last_modify;

	if (cache->time.max_age > 0)
	{
		server_freshness_limit = cache->time.max_age;
		debug_log(3, "server_freshness_limit %ld, max_age set\n", server_freshness_limit);
	}
	else if (cache->time.expires > 0)
	{
		server_freshness_limit = cache->time.expires - cache->time.date;
		debug_log(3, "server_freshness_limit %ld, expires set\n", server_freshness_limit);
	}
	else if (cache->time.last_modified > 0)
	{
		time_since_last_modify = (cache->time.date - cache->time.last_modified) > 0 ? \
									 (cache->time.date - cache->time.last_modified) : 0;
		server_freshness_limit = time_since_last_modify * LM_FACTOR;
		heuristic = 1;
		debug_log(3, "server_freshness_limit %ld, last_modified set\n", server_freshness_limit);
	}
	else
	{
		server_freshness_limit = DEFAULT_CACHE_MIN_LIFETIME;
		heuristic = 1;
		debug_log(3, "server_freshness_limit %ld, all not set\n", server_freshness_limit);
	}

	if (heuristic)
	{
			if (server_freshness_limit > DEFAULT_CACHE_MAX_LIFETIME)
				server_freshness_limit = DEFAULT_CACHE_MAX_LIFETIME;
			if (server_freshness_limit < DEFAULT_CACHE_MIN_LIFETIME)
				server_freshness_limit = DEFAULT_CACHE_MIN_LIFETIME;
	}

	debug_log(3, "life %ld\n", server_freshness_limit);
	return(server_freshness_limit);
}

/* can not cache */
int cache_able(request_t *req)
{
	int statu = CACHE_ABLE_YES;

	if(req)  {
		if(req->cc.no_store) {
			debug_log(3, "'%s%s' request declare no-store\n", req->host, req->url_path);
			statu = CACHE_ABLE_NO;
			goto result;
		}
		if(req->method.id != METHOD_GET) {
			debug_log(5, "%s%s non GET request, return CACHE_ABLE_NO\n", req->host, req->url_path);
			statu = CACHE_ABLE_NO;
			goto result;
		}
	}
	else if(req->reply) {
		reply_t *reply = req->reply;
		switch(reply->statu) {
			case 200:
			case 203:
			case 300:
			case 301:
			case 410:
				statu= CACHE_ABLE_YES;
				break;
			default:
				statu = CACHE_ABLE_NO;
				break;
		}
	} 
	else {
		debug_log(5, "'request %p,  return CACHE_ABLE_NO.\n", req);
		statu = CACHE_ABLE_NO;
		goto result;
	}
		
result:
	debug_log(5, "return %s.\n", cache_able_statu_to_string(statu));
	return statu;
}

int freshness_check(cache_file_t *cache, request_t *request)
{
	if(!cache) {
		debug_log(1, "cache %p is null, return CACHE_IN_STALE\n", cache);
		return CACHE_IN_STALE;
	}
	if(!request) {
		debug_log(1, "request %p is null, return CACHE_IN_STALE\n", cache);
		return CACHE_IN_STALE;
	}
	if(request->cc.no_cache) {
		debug_log(1, "request->cc.no_cache %d, return CACHE_IN_STALE\n", cache);
		return CACHE_IN_STALE;
	}

	int state ;
	int age ;
	int freshness_lifetime = cache->time.freshness_lifetime;
	int interval;

	age = cache_age(cache);
	freshness_lifetime = cache->time.freshness_lifetime;
	if(age < freshness_lifetime) {
		interval = freshness_lifetime - age;
		if(request->cc.min_stale > interval) {
			debug_log(3, "freshness %d < min_stale %d, return CACHE_STALE\n", \
							interval, request->cc.min_stale);
			state = CACHE_IN_STALE;
		}
		else {
			debug_log(3, "freshness %d, return CACHE_FRESH\n", interval);
			state = CACHE_IN_FRESH;
		}
	}
	else {
		interval = age - freshness_lifetime;
		if(request->cc.max_stale > interval) {
			debug_log(3, "staleness %d < max_stale %d, return CACHE_FRESH\n", \
							interval, request->cc.max_stale);
			state = CACHE_IN_FRESH;
		}
		else {
			debug_log(3, "freshness_check staleness %d, return CACHE_STALE\n", interval);
			state = CACHE_IN_STALE;
		}

		debug_log(3, "staleness %d\n", interval);
		state =  CACHE_IN_STALE;
	}
	return state;
}

void cache_control_set(cache_control_t *cc, header_t *header)
{
	if(!cc) {
		warn_log("cc %p is null\n", cc);
		return ;
	}
	if(!header) {
		debug_log(3, "header %p is nil\n", header);
		return;
	}
	
	char *tmp;
	char *token ;
	char *cvalue = NULL;
	header_entry_t *entry = header_entry_get(header, HDR_CACHE_CONTROL);
	if(!entry) {
		debug_log(3, "Cache-Control not found %p\n", entry);
		if(NULL == (entry = header_entry_get(header, HDR_PRAGMA))) {
			debug_log(3, "Pragma not found %p\n", entry);
			return ;
		}
	}

	debug_log(3, "%s\n", entry->value);
	cvalue = strdup(entry->value); /* remaind to free */
	tmp = cvalue;
	token = (char *) strsep(&tmp,  ";, ");
	if(!token) {
		cache_control_parse(cc, cvalue, strlen(cvalue));
	}
	else {
		while(token) {
			cache_control_parse(cc, token, strlen(token));
			token = (char *) strsep(&tmp, ";, ");
		}
	}

	safe_free(cvalue);
}

static void cache_control_parse(cache_control_t *cc, char *value, int size)
{
	assert(cc);
	assert(value);

	const char *token = value;
	
	if(size <= 0) 
		return;
	if(!strncasecmp(token, MAX_STALE, strlen(MAX_STALE))) {
		char *mid = strchr(token, '=');
		if(!mid) {
			debug_log(2, " %s unknown\n", token);
			return;
		}

		while(isspace(*(++mid)));
		cc->max_stale = atoi(mid);
	}
	else if(!strncasecmp(token, MIN_STALE, strlen(MIN_STALE))) {
		char *mid = strchr(token, '=');
		if(!mid) {
			debug_log(2, "%s unknown\n", token);
			return;
		}

		while(isspace(*(++mid)));
		cc->min_stale = atoi(mid);
	}
	else if(!strncasecmp(token, MIN_FRESH, strlen(MIN_FRESH))) {
		char *mid = strchr(token, '=');
		if(!mid) {
			debug_log(2, "%s unknown\n", token);
			return ;
		}

		while(isspace(*(++mid)));
		cc->min_fresh = atoi(mid);
	}
	else if(!strcasecmp(token, PUBLIC)) {
		cc->public = 1;
	}
	else if(!strcasecmp(token, PRIVATE)) {
		cc->private = 1;
	}
	else if(!strncasecmp(token, MAX_AGE, strlen(MAX_AGE))) {
		char *mid = strchr(token, '=');
		if(!mid) {
			debug_log(2, "%s unknown\n", token);
			return;
		}

		while(isspace(*(++mid)));
		cc->max_age = atoi(mid);
	}
	else if(!strncasecmp(token, S_MAXAGE, strlen(MAX_AGE))) {
		char *mid = strchr(token, '=');
		if(!mid) {
			debug_log(2, "%s unknown\n", token);
			return;
		}

		while(isspace(*(++mid)));
		cc->s_maxage = atoi(mid);
	}
	else if(!strcasecmp(token, NO_CACHE))
		cc->no_cache = 1;
	else if(!strcasecmp(token, NO_STORE)) 
		cc->no_store = 1;
	else if(!strcasecmp(token, NO_TRANSFORM)) 
		cc->no_transform = 1;
	else if(!strcasecmp(token, MUST_REVALIDATE))
		cc->must_revalidate = 1;
	else if(!strcasecmp(token, PROXY_REVALIDATE)) 
		cc->proxy_revalidate = 1;
	else {
			debug_log(2, "Cache-Control unknown value %s\n", token);
	}
}

void cache_time_set(request_t *r, cache_file_t *cache)
{
	if(!r || !cache)
		return ;
	if(!r->reply)
		return;

	struct timeval now;
	reply_t *rep = r->reply;
	header_entry_t *entry;
	header_t *reply_header = rep->headers;
	if(!reply_header)
		return;

	if(-1 == gettimeofday(&now, NULL)) 
		error_log("%p failed to get time\n", r);
	
	cache->time.response_got = now.tv_sec;
	cache->time.max_age = rep->cc.max_age;
	if((entry = header_entry_get(reply_header, HDR_DATE)))
		cache->time.date = parse_rfc1123(entry->value, strlen(entry->value));
	else
		cache->time.date = -1;

	if((entry = header_entry_get(reply_header, HDR_EXPIRES)))
		cache->time.expires = parse_rfc1123(entry->value, strlen(entry->value));
	else
		cache->time.expires = -1;

	if((entry = header_entry_get(reply_header, HDR_LAST_MODIFIED)))
		cache->time.last_modified = parse_rfc1123(entry->value, strlen(entry->value));
	else
		cache->time.last_modified = -1;

	if((entry = header_entry_get(reply_header, HDR_AGE)))
		cache->time.header_age = atol(entry->value);
	else
		cache->time.header_age = -1;
	/* freshness lifetime set */
	if(cache->time.freshness_lifetime <= 0)
		cache->time.freshness_lifetime = cache_freshness_lifetime(cache);
}
